import std.array;
import std.conv;
import std.string;
import std.random;

import std.c.windows.windows;
import core.sys.windows.dll;

import std.container;

__gshared string KEYBOARD_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
__gshared string[string][string] ROTORS;
__gshared string[string] REFLECTORS;

__gshared EnigmaMachine[] machines;
__gshared uint machine_count = 0;
__gshared auto instanceLL = DList!EnigmaMachine();

__gshared string rotor_data;
__gshared string ring_data;
__gshared string refl_data;

__gshared auto rnd = Xorshift96(1);

const int _step = 16;

void addData()
{
    ROTORS["I"]["wiring"] = "EKMFLGDQVZNTOWYHXUSPAIBRCJ";
    ROTORS["I"]["stepping"] = "Q";
    ROTORS["II"]["wiring"] = "AJDKSIRUXBLHWTMCQGZNPYFVOE";
    ROTORS["II"]["stepping"] = "E";
    ROTORS["III"]["wiring"] = "BDFHJLCPRTXVZNYEIWGAKMUSQO";
    ROTORS["III"]["stepping"] = "V";
    ROTORS["IV"]["wiring"] = "ESOVPZJAYQUIRHXLNFTGKDCMWB";
    ROTORS["IV"]["stepping"] = "J";
    ROTORS["V"]["wiring"] = "VZBRGITYUPSDNHLXAWMJQOFECK";
    ROTORS["V"]["stepping"] = "Z";
    ROTORS["VI"]["wiring"] = "JPGVOUMFYQBENHZRDKASXLICTW";
    ROTORS["VI"]["stepping"] = "ZM";
    ROTORS["VII"]["wiring"] = "NZJHGRCXMYSWBOUFAIVLPEKQDT";
    ROTORS["VII"]["stepping"] = "ZM";
    ROTORS["VIII"]["wiring"] = "FKQHTLXOCBJSPDZRAMEWNIUYGV";
    ROTORS["VIII"]["stepping"] = "ZM";
    REFLECTORS["B"] = "YRUHQSLDPXNGOKMIEBFZCWVJAT";
    REFLECTORS["C"] = "FVPJIAOYEDRZXWGCTKUQSBNMHL";
    REFLECTORS["B-Thin"] = "ENKQAUYWJICOPBLMDXZVFTHRGS";
    REFLECTORS["C-Thin"] = "RDOBJNTKVEHMLFCWZAXGYIPSUQ";
}

pure nothrow @nogc @safe int very_wtf_modulo_26(int t)
{
    return t < 0 ? t + 26 : t % 26;
}

class Rotor
{
    int[26] entry_map;
    int[26] exit_map;
    int[26] display_map;
    int[26] pos_map;
    int pos = 0;
    int ring_setting;
    char display_val;
    string stepping;

    this(string wiring)
    {
        this(wiring, 0, "");
    }

    this(string wiring, int ring_setting, string stepping)
    {
        string wiring_str = wiring.toUpper();

        for (int i = 0; i < 26; i++)
        {
            this.entry_map[i] = cast(int)wiring_str[i] - cast(int)'A';
        }

        for (int i = 0; i < 26; i++)
        {
            this.exit_map[this.entry_map[i]] = i;
        }

        this.ring_setting = ring_setting;

        for (int i = 0; i < 26; i++)
        {
            this.display_map[i] = very_wtf_modulo_26(i - this.ring_setting);
            this.pos_map[very_wtf_modulo_26(i - this.ring_setting)] = i;
        }

        this.stepping = stepping;

        this.set_display('A');
    }

    public void set_display(char _val)
    {
        char val = [_val].toUpper()[0];

        this.pos = this.display_map[val - cast(int)'A'];
        this.display_val = val;
    }

    public int signal_in(int n)
    {
        return very_wtf_modulo_26(this.entry_map[(n + this.pos) % 26] - this.pos);
    }

    public int signal_out(int n)
    {
        return very_wtf_modulo_26(this.exit_map[(n + this.pos) % 26] - this.pos);
    }

    public bool notch_over_pawl()
    {
        return this.stepping.indexOf(this.display_val) != -1;
    }

    public void rotate()
    {
        this.pos = (this.pos + 1) % 26;
        this.display_val = cast(char)(cast(int)'A' + this.pos_map[this.pos]);
    }
}

Rotor createRotor(string model, int ring_setting)
{
    return new Rotor(ROTORS[model]["wiring"], ring_setting, ROTORS[model]["stepping"]);
}


Rotor createReflector(string model)
{
    return new Rotor(REFLECTORS[model]);
}


class EnigmaMachine
{
    Rotor[3] rotors;
    Rotor reflector;

    private this(Rotor[] rotors, Rotor reflector)
    {
        this.rotors = rotors;
        this.reflector = reflector;
    }

    public static EnigmaMachine fromKeySheet(string str_rotors, string str_ring_settings, string reflector)
    {
        Rotor[3] rotor_list;
        string[] _rotors = split(str_rotors);
        string[] _ring_settings = split(str_ring_settings);
        for (int i = 0; i < 3; i++)
        {
            rotor_list[i] = createRotor(_rotors[i], to!int(_ring_settings[i]));
        }

        return new EnigmaMachine(rotor_list, createReflector(reflector));
    }

    public void setDisplay(char* val)
    {
        for (int i = 2; i >= 0; --i)
        {
            if (KEYBOARD_CHARS.indexOf(val[i]) == -1)
            {
                return;
            }
            this.rotors[i].set_display(val[i]);
        }
    }

    private void _step_rotors()
    {
        this.rotors[2].rotate();
        if (this.rotors[2].notch_over_pawl() || this.rotors[1].notch_over_pawl())
        {
            this.rotors[1].rotate();
        }
        if (this.rotors[1].notch_over_pawl())
        {
            this.rotors[0].rotate();
        }
    }

    private int _electric_signal(int signal_num)
    {
        int pos = signal_num;

        for (int i = 2; i >= 0; --i)
        {
            pos = this.rotors[i].signal_in(pos);
        }

        pos = this.reflector.signal_in(pos);

        for (int i = 0; i < 3; i++)
        {
            pos = this.rotors[i].signal_out(pos);
        }

        return pos;
    }

    public void processText(char* text, int len)
    {
        for (int i = 0; i < len; i++)
        {
            int signal_num = cast(int)text[i] - cast(int)'A';

            if (signal_num < 0 || signal_num > 25) {
                signal_num = 0;
            }

            this._step_rotors();

            text[i] = KEYBOARD_CHARS[this._electric_signal(signal_num)];
        }
    }
}

__gshared HINSTANCE g_hInst;

extern (Windows)
BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
    switch (ulReason)
    {
    case DLL_PROCESS_ATTACH:
        g_hInst = hInstance;
        dll_process_attach( hInstance, true );
        break;
 
    case DLL_PROCESS_DETACH:
        dll_process_detach( hInstance, true );
        break;
 
    case DLL_THREAD_ATTACH:
        dll_thread_attach( true, true );
        break;
 
    case DLL_THREAD_DETACH:
        dll_thread_detach( true, true );
        break;
 
        default:
    }
    return true;
}

extern (C) export
void py_wrapper_initialize(int count, char* rotor, char* ring, char* reflector, int len_rotor, int len_ring, int len_refl)
{
    addData();
    machines.length = count;
    rnd = Xorshift96(unpredictableSeed());
    auto rotors = appender!string;
    auto rings = appender!string;
    auto refls = appender!string;
    for (int i = 0; i < len_rotor; i++)
    {
        rotors.put(rotor[i]);
    }
    for (int i = 0; i < len_ring; i++)
    {
        rings.put(ring[i]);
    }
    for (int i = 0; i < len_refl; i++)
    {
        refls.put(reflector[i]);
    }
    rotor_data = rotors.data;
    ring_data = rings.data;
    refl_data = refls.data;
}

extern (C) export
int py_wrapper_create_enigma_machine()
{
    try
    {
        EnigmaMachine enigma;
        synchronized (EnigmaMachine.classinfo)
        {
            if (!instanceLL.empty())
            {
                enigma = instanceLL.front();
                instanceLL.removeFront();
            }
            else
            {
                enigma = EnigmaMachine.fromKeySheet(rotor_data, ring_data, refl_data);
            }
            if (machine_count >= machines.length)
            {
                machines.length += _step;
            }
            machines[machine_count] = enigma;
            return machine_count++;
        }
    }
    catch
    {
        return -1;
    }

}

extern (C) export
bool py_wrapper_set_display(int machine_id, char* display)
{
    try
    {
        auto i = machines[machine_id];
        if (i is null)
        {
            return false;
        }
        else
        {
            i.setDisplay(display);
        }
        return true;
    }
    catch
    {
        return false;
    }
}

extern (C) export
bool py_wrapper_process_text(int machine_id, char* text, int len)
{
    try
    {
        auto i = machines[machine_id];
        if (i is null)
        {
            return false;
        }
        else
        {
            i.processText(text, len);
        }
        return true;
    }
    catch
    {
        return false;
    }
}

extern (C) export
bool py_wrapper_destroy(int machine_id)
{
    try
    {
        auto i = machines[machine_id];
        if (i is null)
        {
            return false;
        }
        machines[machine_id] = null;
        instanceLL.insertBack(i);
        return true;
    }
    catch
    {
        return false;
    }
}

extern (C) export
bool py_do_upper(char* text, int len)
{
    try
    {
        auto t = appender!string;
        for (int i = 0; i < len; i++)
        {
            auto a = uniform(0, len, rnd);
            if (a < len/6)
            {
                t.put(cast(char)(cast(int)text[i]-32));
            }
            else
            {
                t.put(text[i]);
            }
        }
        string d = t.data;
        for (int i = 0; i < len; i++)
        {
            text[i] = d[i];
        }
        return true;
    }
    catch
    {
        return false;
    }
}